#include <GameEngine/Graphics/openGL.h>
#include <GameEngine/Graphics/Widget/gl_widget.h>
#include <GameEngine/Geometry/Lines/line3d.h>

#include <QtGui/QMouseEvent>
#include <QtCore/QTimer>

#include <iostream>

#ifdef GAMEENGINE_NAMESPACES
namespace GameEngine {
#endif

#ifndef DOXYGEN_IGNORE
#include "gl_widget.moc"
#endif

//=================================================================================


//! Constructor
/*!
 @param parent The parent widget
 @param shareWidget If not nil, this widget will try to share all textures and display
                    lists available to the share widget
 */
GLWidget::GLWidget(QWidget *parent, QGLWidget *shareWidget) :
	clearColor_(0,0,0,0), released_mouse_(false), QGLWidget(parent, shareWidget)
{
	QTimer* timer = new QTimer(this);
	connect(timer, SIGNAL(timeout()), this, SLOT(callPaintGL()));
#ifdef GAMEENGINE_SHOW_FPS
#ifdef _DEBUG
	prev_ = 0.0;
	nb_frames_ = 0;
	nb_frames_max_ = 10;
	timer->start(0);
#else
	timer->start(100);
#endif
#else
	timer->start(100);
#endif
	manipulator_.add_manipulation( CameraManipulator::MouseMiddle, CameraManipulator::EffectPan );
	manipulator_.add_manipulation( CameraManipulator::MouseRight, CameraManipulator::EffectZoom );
	manipulator_.add_manipulation( CameraManipulator::MouseLeft, CameraManipulator::EffectRotate );
	manipulator_.pan_speed() = 0.001f;
	manipulator_.zoom_speed() = 0.001f;
	manipulator_.rotation_speed() = 1.0f;
	setMouseTracking(true);
}

//! Destructor
GLWidget::~GLWidget() {}

//! Give a hint about the minimum size of the widget
/*!
 @return The minimum size hint for the widget
 */
QSize GLWidget::minimumSizeHint() const {
	return QSize(50, 50);
}

//! Give a hint about the size of the widget
/*!
 @return The size hint for the widget
 */
QSize GLWidget::sizeHint() const {
	return QSize(800, 800);
}

//! Change the background color of the widget
/*!
 @param color The new RGBA background color
 */
void GLWidget::setClearColor(const ColorRGBA& color) {
	clearColor_ = color;
	updateGL();
}

//! Accessor to the scene drawn by the widget (read only)
/*!
 @return A constant reference to the scene drawn by the widget
 */
const Scene& GLWidget::scene() const {
	return scene_;
}

//! Accessor to the scene drawn by the widget (read-write)
/*!
 @return A reference to the scene drawn by the widget
 */
Scene& GLWidget::scene() {
	return scene_;
}

//! Accessor to the camera manipulator (read-write)
/*!
 @return A pointer to the camera manipulator
 */
CameraManipulator* GLWidget::manipulator() {
	return &manipulator_;
}

//! Accessor to the camera manipulator (read only)
/*!
 @return A constant pointer to the camera manipulator
 */
const CameraManipulator* GLWidget::manipulator() const {
	return &manipulator_;
}

//! Initialize the OpenGL state of the widget
void GLWidget::initializeGL() {
	glewInit();

	glEnable( GL_LIGHTING );
	glEnable( GL_COLOR_MATERIAL );
	glEnable( GL_DEPTH_TEST );
	glDepthFunc( GL_LESS );
}

//! Render the scene associated to the widget (slot)
void GLWidget::callPaintGL() {
	paintGL();
}

//! Render the scene associated to the widget
void GLWidget::paintGL() {
	// Reset the background color
	glClearColor( clearColor_.r(), clearColor_.g(), clearColor_.b(), clearColor_.a() );
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
	glClearDepth( 1 );

	// Apply the camera transformations
	glLoadIdentity();
	manipulator_.camera().apply_camera();

	// Compute ray at mouse position (picking ray)
	double mvm[16], pm[16];
	int viewport[4];
	glGetDoublev(GL_MODELVIEW_MATRIX,mvm);
	glGetDoublev(GL_PROJECTION_MATRIX,pm);
	glGetIntegerv(GL_VIEWPORT,viewport);
	double x0, y0, z0, x1, y1, z1;
	gluUnProject(lastPos_.x(),viewport[3]-lastPos_.y(),0.0,mvm,pm,viewport,&x0,&y0,&z0);
	gluUnProject(lastPos_.x(),viewport[3]-lastPos_.y(),1.0,mvm,pm,viewport,&x1,&y1,&z1);
	Line3d<float> ray(Point3d<float>(x0,y0,z0),Point3d<float>(x1,y1,z1));

	// Draw the whole scene
	scene_.prepare_scene();
	scene_.draw_scene(ray);
	scene_.finish_scene();

	// Show FPS
#ifdef GAMEENGINE_SHOW_FPS
#ifdef _DEBUG
	++nb_frames_;
	if ( nb_frames_ >= nb_frames_max_ ) {
		Time::Seconds tmp = timer_.elapsed_time();
		nb_frames_max_ = (int) ( (Time::Seconds)nb_frames_ / ( tmp - prev_ ) );
		std::cout << "Fps = " << nb_frames_max_ << std::endl << std::flush;
		if ( nb_frames_max_ == 0 ) nb_frames_max_ = 1;
		nb_frames_ = 0;
		prev_ = timer_.elapsed_time();
	}
#endif
#endif
}

//! Resize the widget
void GLWidget::resizeGL(int width, int height) {
	// Setup the viewport
	glViewport(0,0,width,height);

	// Setup the projection matrix
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	float side = qMin(width, height);
	glFrustum(-0.05*(float)width/side, 0.05*(float)width/side, -0.05*(float)height/side, 0.05*(float)height/side, 1.0, 100.0);

	// Go back to the modelview matrix
	glMatrixMode(GL_MODELVIEW);
}

//! Function called when the mouse is pressed
void GLWidget::mousePressEvent(QMouseEvent* event) {
	released_mouse_ = true;
	lastPos_ = event->pos();
}

//! Function called when the mouse is moved
void GLWidget::mouseMoveEvent(QMouseEvent* event) {
	if ( released_mouse_ == true ) {
		int dx = event->x() - lastPos_.x();
		int dy = event->y() - lastPos_.y();

		if (event->buttons() & Qt::LeftButton) {
			manipulator_.manipulate( CameraManipulator::MouseLeft, Vector2d<int>(dx,dy) );
		}
		else if (event->buttons() & Qt::RightButton) {
			manipulator_.manipulate( CameraManipulator::MouseRight, Vector2d<int>(dx,dy) );
		}
		else if (event->buttons() & Qt::MiddleButton) {
			manipulator_.manipulate( CameraManipulator::MouseMiddle, Vector2d<int>(dx,dy) );
		}
	}

	lastPos_ = event->pos();
	updateGL();
}

//! Function called when the mouse is released
void GLWidget::mouseReleaseEvent(QMouseEvent*) {
	released_mouse_ = false;
	emit clicked();
}


#ifdef GAMEENGINE_NAMESPACES
} // namespace GameEngine
#endif
